package it.bifulco.luigi.motiondetection.prototype;

/**
 * Copyright (c) 2006 - 2012 Smaxe Ltd (www.smaxe.com).
 * All rights reserved.
 */

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

import javax.swing.AbstractAction;
import javax.swing.AbstractListModel;
import javax.swing.ComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;

import com.smaxe.uv.media.VideoFrameFactory;
import com.smaxe.uv.media.core.VideoFrame;
import com.smaxe.uv.media.swing.JVideoScreen;
import com.smaxe.uv.na.WebcamFactory;
import com.smaxe.uv.na.webcam.IWebcam;

/**
 * <code>ExNativeAccessWebcam</code> - {@link IWebcam} usage example.
 * 
 */
public final class ExNativeAccessWebcam extends Object {

	public static PriorityBlockingQueue<MotionDetectionEntry> queue = new PriorityBlockingQueue<MotionDetectionEntry>(
			100, new Comparator<MotionDetectionEntry>() {

				@Override
				public int compare(MotionDetectionEntry o1,
						MotionDetectionEntry o2) {
					return o2.getTimestamp().compareTo(o1.getTimestamp());
				}
			});
	public static Processor processor;

	/**
	 * Entry point.
	 * 
	 * @param args
	 * @throws Exception
	 *             if an exception occurred
	 */
	public static void main(final String[] args) throws Exception {

		final JComboBox webcamComboBox = new JComboBox();

		final JFrame frame = new JFrame();

		final JPanel content = new JPanel(new FlowLayout());

		content.setBorder(new EmptyBorder(8, 8, 8, 8));
		content.setPreferredSize(new Dimension(640, 48));

		content.add(new JLabel("Webcam: ", JLabel.RIGHT));
		content.add(webcamComboBox);
		content.add(new JButton(new AbstractAction("Open") {
			private final static long serialVersionUID = -4792981545160764997L;

			public void actionPerformed(ActionEvent e) {
				final IWebcam webcam = (IWebcam) webcamComboBox
						.getSelectedItem();
				if (webcam == null)
					return;

				final AtomicReference<JFrame> frameRef = new AtomicReference<JFrame>();

				final JVideoScreen videoScreen = new JVideoScreen();
				final AtomicBoolean videoScreenFlip = new AtomicBoolean(false);
				final AtomicBoolean videoScreenMirror = new AtomicBoolean(false);

				new Thread(new Runnable() {
					public void run() {
						final AtomicReference<VideoFrame> lastFrameRef = new AtomicReference<VideoFrame>();

						try {
							webcam.open(new IWebcam.FrameFormat(320, 240),
									new IWebcam.IListener() {
										private VideoFrame lastFrame = new VideoFrame(
												0, 0, null);

										public void onVideoFrame(
												final VideoFrame frame) {
											SwingUtilities
													.invokeLater(new Runnable() {
														public void run() {
															videoScreen
																	.setFrame(frame);

															if (lastFrame.width != frame.width
																	|| lastFrame.height != frame.height) {
																final JFrame frame = frameRef
																		.get();

																if (frame != null)
																	frame.pack();
															}

															lastFrame = frame;

															lastFrameRef
																	.set(lastFrame);

															MotionDetectionEntry entry = new MotionDetectionEntry();
															entry.setTimestamp(frame.timestamp);
															entry.setImage(VideoFrameFactory
																	.toBufferedImage(frame));

															queue.add(entry);
															System.out
																	.println(queue
																			.size());

														}
													});
										}
									});

							webcam.startCapture();

							SwingUtilities.invokeLater(new Runnable() {
								public void run() {
									final JFrame frame = new JFrame();

									frameRef.set(frame);

									frame.getContentPane().setLayout(
											new BorderLayout());
									frame.getContentPane().add(videoScreen,
											BorderLayout.CENTER);

									frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
									frame.setResizable(false);
									frame.setTitle(webcam.getName());

									videoScreen
											.addMouseListener(new MouseAdapter() {
												@Override
												public void mouseClicked(
														final MouseEvent e) {
													final int clickCount = e
															.getClickCount();
													final Object source = e
															.getSource();

													switch (clickCount) {
													case 1: {
														if (SwingUtilities
																.isRightMouseButton(e)) {
															JPopupMenu popup = new JPopupMenu();

															popup.add(new AbstractAction(
																	"Take Snapshot") {
																private final static long serialVersionUID = 0L;

																public void actionPerformed(
																		ActionEvent e) {
																	new SnapshotDialog(
																			VideoFrameFactory
																					.clone(lastFrameRef
																							.get()))
																			.setVisible(true);
																}
															});

															popup.addSeparator();

															popup.add(new AbstractAction(
																	"Mirror") {
																private final static long serialVersionUID = 0L;

																public void actionPerformed(
																		ActionEvent e) {
																	videoScreenMirror
																			.set(!videoScreenMirror
																					.get());
																	videoScreen
																			.mirror(videoScreenMirror
																					.get());
																}
															});

															popup.add(new AbstractAction(
																	"Flip") {
																private final static long serialVersionUID = 0L;

																public void actionPerformed(
																		ActionEvent e) {
																	videoScreenFlip
																			.set(!videoScreenFlip
																					.get());
																	videoScreen
																			.flip(videoScreenFlip
																					.get());
																}
															});

															popup.addSeparator();

															popup.add(new AbstractAction(
																	"160x120") {
																private final static long serialVersionUID = 0L;

																public void actionPerformed(
																		ActionEvent e) {
																	webcam.setFrameFormat(new IWebcam.FrameFormat(
																			160,
																			120));
																}
															});

															popup.add(new AbstractAction(
																	"320x240") {
																private final static long serialVersionUID = 0L;

																public void actionPerformed(
																		ActionEvent e) {
																	webcam.setFrameFormat(new IWebcam.FrameFormat(
																			320,
																			240));
																}
															});

															popup.add(new AbstractAction(
																	"640x480") {
																private final static long serialVersionUID = 0L;

																public void actionPerformed(
																		ActionEvent e) {
																	webcam.setFrameFormat(new IWebcam.FrameFormat(
																			640,
																			480));
																}
															});

															popup.show(
																	(Component) source,
																	e.getX(),
																	e.getY());
														}
													}
														break;
													}
												}
											});

									frame.addWindowListener(new WindowAdapter() {
										@Override
										public void windowClosing(WindowEvent e) {
											webcam.close();
										}
									});

									frame.pack();
									frame.setVisible(true);
								}
							});
						} catch (Exception ex) {
							JOptionPane.showMessageDialog(frame,
									ex.getMessage(), ex.getMessage(),
									JOptionPane.WARNING_MESSAGE);
						}
					}
				}).start();
			}
		}));

		frame.getContentPane().setLayout(new BorderLayout());
		frame.getContentPane().add(content);

		frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		frame.setTitle("Webcam capture options");

		frame.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});

		frame.pack();
		frame.setVisible(true);

		webcamComboBox.setModel(new WebcamComboModel(WebcamFactory.getWebcams(
				frame, args.length == 0 ? "jitsi" : args[0])));
		final ImageCompare ic = new ImageCompare();

		processor = new Processor();
		processor.setListener(new ProcessorListener() {

			@Override
			public void process(MotionDetectionEntry a, MotionDetectionEntry b) {
				System.out.println("Processing: " + a + " - " + b);
				boolean alert = false;
				boolean[] mask = new boolean[2];
				ic.setImg1(b.getImage());
				ic.setImg2(a.getImage());
				ic.setDebugMode(0);
				String s = "";
				ic.setParameters(4, 4, 1, 8);
				boolean match = ic.compare();
				if (match) {
					for (int i = 0; i < mask.length; i++) {
						if (i == 0) {
							ic.setParameters(4, 4, 1, 1);
						} else {
							ic.setParameters(4, 4, 10, 1);
						}
						mask[i] = ic.compare();
						s += mask[i] + ",";
					}
					s = "";
					if (mask[0] && mask[1]) {
						for (int i = 0; i < mask.length; i++) {
							if (i == 0) {
								ic.setParameters(8, 8, 1, 1);
							} else {
								ic.setParameters(8, 8, 10, 1);
							}
							mask[i] = ic.compare();
							s += mask[i] + ",";
						}
						if (!mask[0] || !mask[1]) {
							alert = true;

						}
					} else {
						alert = true;

					}
				} else {
					alert = true;

				}
				if (alert) {
					Toolkit.getDefaultToolkit().beep();
					// ic.saveJPG(ic.getChangeIndicator(), "tmp/change"
					// + UUID.randomUUID().toString() + ".jpg");
				} else {
					// try {
					// if (!ImageUtils.image2MD5(a.getImage()).equals(
					// ImageUtils.image2MD5(b.getImage()))) {
					// System.out.println("MD5 ALERT!!!");
					// Toolkit.getDefaultToolkit().beep();
					// }
					// } catch (Exception e) {
					//
					// }
					ic.setParameters(2, 2, 1, 1);
					if (!ic.compare()) {
						Toolkit.getDefaultToolkit().beep();
					} else {
//						ic.setParameters(200, 200, 1, 1);
//						if (!ic.compare()) {
//							Toolkit.getDefaultToolkit().beep();
//						}
					}
				}
				System.out.println(s);
				// Display some indication of the differences in the image.

				// Compare.
				// boolean match =
				// System.out.println(match);
				// if (!match) {
				// Toolkit.getDefaultToolkit().beep();
				// }

			}
		});

		Thread t = new Thread(processor);
		t.start();
		t.setPriority(Thread.MAX_PRIORITY);
		// t.setPriority(Thread.MAX_PRIORITY);

	}

	/**
	 * <code>SnapshotDialog</code> - snapshot dialog.
	 */
	private final static class SnapshotDialog extends JDialog {
		private final static long serialVersionUID = -3136925779409522531L;

		/**
		 * Constructor.
		 * 
		 * @param videoFrame
		 */
		public SnapshotDialog(final VideoFrame videoFrame) {
			this.setLayout(new BorderLayout());
			this.setResizable(false);
			this.setTitle("Photo snapshot");

			JVideoScreen videoScreen = new JVideoScreen();

			videoScreen.setFrame(videoFrame);

			videoScreen.addMouseListener(new MouseAdapter() {
				@Override
				public void mouseClicked(final MouseEvent e) {
					final int clickCount = e.getClickCount();
					final Object source = e.getSource();

					switch (clickCount) {
					case 1: {
						if (SwingUtilities.isRightMouseButton(e)) {
							JPopupMenu popup = new JPopupMenu();

							popup.add(new AbstractAction("Save As..") {
								private final static long serialVersionUID = 0L;

								public void actionPerformed(ActionEvent e) {
									JFileChooser fileChooser = new JFileChooser();

									fileChooser.setCurrentDirectory(new File(
											"."));

									final int rv = fileChooser
											.showSaveDialog(getParent());

									if (rv != JFileChooser.APPROVE_OPTION)
										return;

									final File file = fileChooser
											.getSelectedFile();

									new Thread(new Runnable() {
										public void run() {
											try {

												VideoFrameFactory.saveAsJpg(
														file, videoFrame);
											} catch (IOException e) {
												e.printStackTrace();
											}
										}
									}).start();
								}
							});

							popup.show((Component) source, e.getX(), e.getY());
						}
					}
						break;
					}
				}
			});

			this.add(videoScreen, BorderLayout.CENTER);
			this.pack();
		}
	}

	/**
	 * <code>WebcamComboModel</code> - webcam combo model.
	 */
	private final static class WebcamComboModel extends AbstractListModel
			implements ComboBoxModel {
		private final static long serialVersionUID = -8627944517955777531L;

		// fields
		private final List<IWebcam> devices;
		// state
		private Object selected = null;

		/**
		 * Constructor.
		 * 
		 * @param devices
		 */
		public WebcamComboModel(List<IWebcam> devices) {
			this.devices = devices;
		}

		// MutableComboBoxModel implementation

		public void setSelectedItem(final Object item) {
			this.selected = item;
		}

		public Object getSelectedItem() {
			return selected;
		}

		public Object getElementAt(int index) {
			return devices.get(index);
		}

		public int getSize() {
			return devices.size();
		}
	}

	public static interface ProcessorListener {
		public void process(MotionDetectionEntry a, MotionDetectionEntry b);

	}

	public static class Processor implements Runnable {

		MotionDetectionEntry prev;
		MotionDetectionEntry current;
		ProcessorListener listener;

		ReentrantLock lock = new ReentrantLock();

		@Override
		public void run() {
			while (true) {
				try {
					if (lock.isLocked()) {
						lock.unlock();
					}
					lock.lock();
					MotionDetectionEntry entry = queue.poll(1,
							TimeUnit.MILLISECONDS);
					if (entry != null) {
						if (current != null && prev != null) {
							if (listener != null) {
								// new Thread(new Runnable() {

								// //@Override
								// public void run() {
								listener.process(
										MotionDetectionEntry.copy(prev),
										MotionDetectionEntry.copy(current));
								current = null;
								prev = null;

								// }
								// }).start();

							}

						} else {
							if (current == null) {
								current = entry;
								continue;
							}
							if (prev == null && current != null) {
								prev = current;
								current = entry;
								continue;
							}
						}
					}
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} finally {
					lock.unlock();
				}

			}
		}

		public ProcessorListener getListener() {
			return listener;
		}

		public void setListener(ProcessorListener listener) {
			this.listener = listener;
		}

	}

}